/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */

/* 
 * File:   IceTransport.cpp
 * Author: user
 * 
 * Created on June 5, 2017, 9:51 AM
 */


#include "IceTransport.h"

IceTransport::IceTransport() {
}

IceTransport::IceTransport(const IceTransport& orig) {
}

IceTransport::~IceTransport() {
}

/* Utility to display error messages */
void IceTransport::printError(const char *title, pj_status_t status) {
    char errmsg[PJ_ERR_MSG_SIZE];

    pj_strerror(status, errmsg, sizeof (errmsg));
    PJ_LOG(1, (THIS_FILE, "%s: %s", title, errmsg));
}

void IceTransport::setOptions() {

    opt.max_host = 10;

    opt.ns.ptr = "211.167.97.67";
    opt.ns.slen = strlen("211.167.97.67");

    opt.regular = PJ_TRUE;

    opt.stun_srv.ptr = "stun.xten.com";
    opt.stun_srv.slen = strlen("stun.xten.com");
    opt.stun_srv.ptr = "10.0.2.15:34780";
    opt.stun_srv.slen = strlen("10.0.2.15:34780");
    // opt.stun_srv.slen = 0;

    opt.turn_srv.ptr = opt.stun_srv.ptr;
    opt.turn_srv.slen = opt.stun_srv.slen;
    opt.turn_tcp = PJ_FALSE;
    opt.turn_username.ptr = "100";
    opt.turn_username.slen = strlen("100");
    opt.turn_password.ptr = "100";
    opt.turn_password.slen = strlen("100");

    opt.comp_cnt = 2;
}

void IceTransport::setRole(unsigned role) {
    this->role = role;
}

void IceTransport::setName(char* name) {
    strcpy(this->name, name);
}

bool IceTransport::init() {

    pj_status_t status = 0;

    /* Initialize the libraries before anything else */
    status = pj_init();
    status = pjlib_util_init();
    status = pjnath_init();

    /* Must create pool factory, where memory allocations come from */
    pj_caching_pool_init(&cp, NULL, 0);

    /* Init our ICE settings with null values */
    pj_ice_strans_cfg_default(&ice_cfg);

    ice_cfg.stun_cfg.pf = &cp.factory;

    /* Create application memory pool */
    pool = pj_pool_create(&cp.factory, name, 512, 512, NULL);

    /* Create timer heap for timer stuff */
    status = pj_timer_heap_create(pool, 100, &ice_cfg.stun_cfg.timer_heap);

    /* and create ioqueue for network I/O stuff */
    status = pj_ioqueue_create(pool, 16, &ice_cfg.stun_cfg.ioqueue);

    /* something must poll the timer heap and ioqueue, 
     * unless we're on Symbian where the timer heap and ioqueue run
     * on themselves.
     */
    status = pj_thread_create(pool, name, &(IceTransport::workerThread),
            this, 0, 0, &thread);

    ice_cfg.af = pj_AF_INET();

    /* Create DNS resolver if nameserver is set */
    if (opt.ns.slen) {
        status = pj_dns_resolver_create(&cp.factory,
                "resolver",
                0,
                ice_cfg.stun_cfg.timer_heap,
                ice_cfg.stun_cfg.ioqueue,
                &ice_cfg.resolver);

        pj_dns_resolver_set_ns(ice_cfg.resolver, 1, &opt.ns, NULL);
    }

    /* -= Start initializing ICE stream transport config =- */
    /* Maximum number of host candidates */
    if (opt.max_host != -1)
        ice_cfg.stun.max_host_cands = opt.max_host;

    /* Nomination strategy */
    if (opt.regular)
        ice_cfg.opt.aggressive = PJ_FALSE;
    else
        ice_cfg.opt.aggressive = PJ_TRUE;

    /* Configure STUN/srflx candidate resolution */
    if (opt.stun_srv.slen) {
        char *pos;

        /* option may contain port number */
        if ((pos = pj_strchr(&opt.stun_srv, ':')) != NULL) {
            ice_cfg.stun.server.ptr = opt.stun_srv.ptr;
            ice_cfg.stun.server.slen = (pos - opt.stun_srv.ptr);

            ice_cfg.stun.port = (pj_uint16_t) atoi(pos + 1);
        } else {
            ice_cfg.stun.server = opt.stun_srv;
            ice_cfg.stun.port = PJ_STUN_PORT;
        }

        /* For this demo app, configure longer STUN keep-alive time
         * so that it does't clutter the screen output.
         */
        ice_cfg.stun.cfg.ka_interval = KA_INTERVAL;
    }

    /* Configure TURN candidate */
    if (opt.turn_srv.slen) {
        char *pos;

        /* Command line option may contain port number */
        if ((pos = pj_strchr(&opt.turn_srv, ':')) != NULL) {
            ice_cfg.turn.server.ptr = opt.turn_srv.ptr;
            ice_cfg.turn.server.slen = (pos - opt.turn_srv.ptr);

            ice_cfg.turn.port = (pj_uint16_t) atoi(pos + 1);
        } else {
            ice_cfg.turn.server = opt.turn_srv;
            ice_cfg.turn.port = PJ_STUN_PORT;
        }

        /* TURN credential */
        ice_cfg.turn.auth_cred.type = PJ_STUN_AUTH_CRED_STATIC;
        ice_cfg.turn.auth_cred.data.static_cred.username = opt.turn_username;
        ice_cfg.turn.auth_cred.data.static_cred.data_type = PJ_STUN_PASSWD_PLAIN;
        ice_cfg.turn.auth_cred.data.static_cred.data = opt.turn_password;

        /* Connection type to TURN server */
        if (opt.turn_tcp) {
            ice_cfg.turn.conn_type = PJ_TURN_TP_TCP;
        } else {
            ice_cfg.turn.conn_type = PJ_TURN_TP_UDP;
        }

        /* For this demo app, configure longer keep-alive time
         * so that it does't clutter the screen output.*/
        ice_cfg.turn.alloc_param.ka_interval = KA_INTERVAL;
    }
}

bool IceTransport::deinit() {
    if (icest)
        pj_ice_strans_destroy(icest);

    pj_thread_sleep(500);

    thread_quit_flag = PJ_TRUE;
    if (thread) {
        pj_thread_join(thread);
        pj_thread_destroy(thread);
    }

    if (ice_cfg.stun_cfg.ioqueue)
        pj_ioqueue_destroy(ice_cfg.stun_cfg.ioqueue);

    if (ice_cfg.stun_cfg.timer_heap)
        pj_timer_heap_destroy(ice_cfg.stun_cfg.timer_heap);

    pj_caching_pool_destroy(&cp);

    pj_shutdown();
}

bool IceTransport::start() {

    createInstance();
    initSession(role);

    return 0;
}

bool IceTransport::stop() {

    stopSession();
    destroyInstance();

    return 0;
}

/*
 * This is the worker thread that polls event in the background.
 */
int IceTransport::workerThread(void* data) {
    IceTransport* instance = (IceTransport*) data;

    while (!instance->thread_quit_flag) {
        handleEevents(500, NULL, instance);
    }

    return 0;
}

/*
 * This function checks for events from both timer and ioqueue (for
 * network events). It is invoked by the worker thread.
 */
pj_status_t IceTransport::handleEevents(unsigned max_msec, unsigned *p_count, IceTransport* instance) {

    enum {
        MAX_NET_EVENTS = 1
    };
    pj_time_val max_timeout = {0, 0};
    pj_time_val timeout = {0, 0};
    unsigned count = 0, net_event_count = 0;
    int c;

    max_timeout.msec = max_msec;

    /* Poll the timer to run it and also to retrieve the earliest entry. */
    timeout.sec = timeout.msec = 0;
    c = pj_timer_heap_poll(instance->ice_cfg.stun_cfg.timer_heap, &timeout);
    if (c > 0)
        count += c;

    /* timer_heap_poll should never ever returns negative value, or otherwise
     * ioqueue_poll() will block forever!
     */
    pj_assert(timeout.sec >= 0 && timeout.msec >= 0);
    if (timeout.msec >= 1000) timeout.msec = 999;

    /* compare the value with the timeout to wait from timer, and use the 
     * minimum value. 
     */
    if (PJ_TIME_VAL_GT(timeout, max_timeout))
        timeout = max_timeout;

    /* Poll ioqueue. 
     * Repeat polling the ioqueue while we have immediate events, because
     * timer heap may process more than one events, so if we only process
     * one network events at a time (such as when IOCP backend is used),
     * the ioqueue may have trouble keeping up with the request rate.
     *
     * For example, for each send() request, one network event will be
     *   reported by ioqueue for the send() completion. If we don't poll
     *   the ioqueue often enough, the send() completion will not be
     *   reported in timely manner.
     */
    do {
        c = pj_ioqueue_poll(instance->ice_cfg.stun_cfg.ioqueue, &timeout);
        if (c < 0) {
            pj_status_t err = pj_get_netos_error();
            pj_thread_sleep(PJ_TIME_VAL_MSEC(timeout));
            if (p_count)
                *p_count = count;
            return err;
        } else if (c == 0) {
            break;
        } else {
            net_event_count += c;
            timeout.sec = timeout.msec = 0;
        }
    } while (c > 0 && net_event_count < MAX_NET_EVENTS);

    count += net_event_count;
    if (p_count)
        *p_count = count;

    return PJ_SUCCESS;
}

/*
 * Create ICE stream transport instance, invoked from the menu.
 */
void IceTransport::createInstance(void) {
    pj_ice_strans_cb icecb;
    pj_status_t status;

    if (icest != NULL) {
        puts("ICE instance already created, destroy it first");
        return;
    }

    /* init the callback */
    pj_bzero(&icecb, sizeof (icecb));
    icecb.on_rx_data = onReceiveData;
    icecb.on_ice_complete = onIceComplete;

    /* create the instance */
    status = pj_ice_strans_create(name, /* object name  */
            &ice_cfg, /* settings	    */
            opt.comp_cnt, /* comp_cnt	    */
            NULL, /* user data    */
            &icecb, /* callback	    */
            &icest) /* instance ptr */
            ;
    if (status != PJ_SUCCESS) {
        printError("error creating ice", status);
    } else {
        PJ_LOG(3, (THIS_FILE, "ICE instance successfully created"));
    }
}

/*
 * This is the callback that is registered to the ICE stream transport to
 * receive notification about incoming data. By "data" it means application
 * data such as RTP/RTCP, and not packets that belong to ICE signaling (such
 * as STUN connectivity checks or TURN signaling).
 */
void IceTransport::onReceiveData(pj_ice_strans *ice_st,
        unsigned comp_id,
        void *pkt, pj_size_t size,
        const pj_sockaddr_t *src_addr,
        unsigned src_addr_len) {
    char ipstr[PJ_INET6_ADDRSTRLEN + 10];

    PJ_UNUSED_ARG(ice_st);
    PJ_UNUSED_ARG(src_addr_len);
    PJ_UNUSED_ARG(pkt);

    // Don't do this! It will ruin the packet buffer in case TCP is used!
    //((char*)pkt)[size] = '\0';

    PJ_LOG(3, (THIS_FILE, "Component %d: received %d bytes data from %s: \"%.*s\"",
            comp_id, size,
            pj_sockaddr_print(src_addr, ipstr, sizeof (ipstr), 3),
            (unsigned) size,
            (char*) pkt));
}

/*
 * This is the callback that is registered to the ICE stream transport to
 * receive notification about ICE state progression.
 */
void IceTransport::onIceComplete(pj_ice_strans *ice_st,
        pj_ice_strans_op op,
        pj_status_t status) {
    const char *opname =
            (op == PJ_ICE_STRANS_OP_INIT ? "initialization" :
            (op == PJ_ICE_STRANS_OP_NEGOTIATION ? "negotiation" : "unknown_op"));

    if (status == PJ_SUCCESS) {
        PJ_LOG(3, (THIS_FILE, "ICE %s successful", opname));
    } else {
        char errmsg[PJ_ERR_MSG_SIZE];

        pj_strerror(status, errmsg, sizeof (errmsg));
        PJ_LOG(1, (THIS_FILE, "ICE %s failed: %s", opname, errmsg));
        pj_ice_strans_destroy(ice_st);

        //TODO icest = NULL;
    }
}

/*
 * Destroy ICE stream transport instance, invoked from the menu.
 */
void IceTransport::destroyInstance(void) {
    if (icest == NULL) {
        PJ_LOG(1, (THIS_FILE, "Error: No ICE instance, create it first"));
        return;
    }

    pj_ice_strans_destroy(icest);
    icest = NULL;

    resetRemoteInfo();

    PJ_LOG(3, (THIS_FILE, "ICE instance destroyed"));
}

void IceTransport::resetRemoteInfo(void) {
    pj_bzero(&rem, sizeof (rem));
}

/*
 * Create ICE session, invoked from the menu.
 */
void IceTransport::initSession(unsigned rolechar) {
    pj_ice_sess_role role = (pj_tolower((pj_uint8_t) rolechar) == 'o' ?
            PJ_ICE_SESS_ROLE_CONTROLLING :
            PJ_ICE_SESS_ROLE_CONTROLLED);
    pj_status_t status;

    if (icest == NULL) {
        PJ_LOG(1, (THIS_FILE, "Error: No ICE instance, create it first"));
        return;
    }

    if (pj_ice_strans_has_sess(icest)) {
        PJ_LOG(1, (THIS_FILE, "Error: Session already created"));
        return;
    }

    status = pj_ice_strans_init_ice(icest, role, NULL, NULL);
    if (status != PJ_SUCCESS) {
        printError("error creating session", status);
    } else {
        PJ_LOG(3, (THIS_FILE, "ICE session created"));
    }

    resetRemoteInfo();
}

/*
 * Stop/destroy ICE session, invoked from the menu.
 */
void IceTransport::stopSession(void) {
    pj_status_t status;

    if (icest == NULL) {
        PJ_LOG(1, (THIS_FILE, "Error: No ICE instance, create it first"));
        return;
    }

    if (!pj_ice_strans_has_sess(icest)) {
        PJ_LOG(1, (THIS_FILE, "Error: No ICE session, initialize first"));
        return;
    }

    status = pj_ice_strans_stop_ice(icest);
    if (status != PJ_SUCCESS) {
        printError("error stopping session", status);
    } else {
        PJ_LOG(3, (THIS_FILE, "ICE session stopped"));
    }

    resetRemoteInfo();
}

/*
 * Show information contained in the ICE stream transport. This is
 * invoked from the menu.
 */
void IceTransport::showIceInfo(void) {
    char buffer[2000] = {0};
    int len = 0;
    pj_bool_t state = 0;

    if (icest == NULL) {
        PJ_LOG(1, (THIS_FILE, "Error: No ICE instance, create it first"));
        return;
    }

    state = pj_ice_strans_sess_is_complete(icest);
    if (state) {
        puts("negotiation complete");
    }
    state = pj_ice_strans_sess_is_running(icest);
    if (state) {
        puts("session ready");
    }
    state = pj_ice_strans_has_sess(icest);
    if (state) {
        puts("session ready");
    } else {
        puts("Create the session first to see more info");
        return;
    }

    pj_ice_strans_get_running_comp_cnt(icest);

    pj_ice_strans_get_role(icest);

    len = getLocalSDP(buffer, sizeof (buffer));
    if (len < 0) {
        PJ_LOG(1, (THIS_FILE, "error: not enough buffer to show ICE SDP"));
    }

    puts("");
    printf("Local SDP (paste this to remote host):\n"
            "--------------------------------------\n"
            "%s\n", buffer);


    puts("");
    puts("Remote info:\n"
            "----------------------");
    if (rem.cand_cnt == 0) {
        puts("No remote info yet");
    } else {
        unsigned i;

        printf("Remote ufrag       : %s\n", rem.ufrag);
        printf("Remote password    : %s\n", rem.pwd);
        printf("Remote cand. cnt.  : %d\n", rem.cand_cnt);

        for (i = 0; i < rem.cand_cnt; ++i) {
            len = printCandidate(buffer, sizeof (buffer), &rem.cand[i]);
            if (len < 0) {
                PJ_LOG(1, (THIS_FILE, "error: not enough buffer to show ICE candidate"));
            }

            printf("  %s", buffer);
        }
    }

}

int IceTransport::getLocalSDP(char* buffer, unsigned maxlen) {
    int len = 0;
    pj_bool_t state = 0;

    state = pj_ice_strans_has_sess(icest);

    if (state) {
        len = encodeSessionInfo(buffer, maxlen);
    }

    return len;
}

/* 
 * Encode ICE information in SDP.
 */
int IceTransport::encodeSessionInfo(char* buffer, unsigned maxlen) {
    char *p = buffer;
    unsigned comp;
    int printed;
    pj_str_t local_ufrag, local_pwd;
    pj_status_t status;

    /* Write "dummy" SDP v=, o=, s=, and t= lines */
    printed = snprintf(p, maxlen - (p - buffer), "v=0\no=- 3414953978 3414953978 IN IP4 localhost\ns=ice\nt=0 0\n");
    p += printed;

    /* Get ufrag and pwd from current session */
    pj_ice_strans_get_ufrag_pwd(icest, &local_ufrag, &local_pwd,
            NULL, NULL);

    /* Write the a=ice-ufrag and a=ice-pwd attributes */
    printed = snprintf(p, maxlen - (p - buffer), "a=ice-ufrag:%.*s\na=ice-pwd:%.*s\n", (int) local_ufrag.slen, local_ufrag.ptr, (int) local_pwd.slen, local_pwd.ptr);
    p += printed;

    /* Write each component */
    for (comp = 0; comp < opt.comp_cnt; ++comp) {
        unsigned j, cand_cnt;
        pj_ice_sess_cand cand[PJ_ICE_ST_MAX_CAND];
        char ipaddr[PJ_INET6_ADDRSTRLEN];

        /* Get default candidate for the component */
        status = pj_ice_strans_get_def_cand(icest, comp + 1, &cand[0]);
        if (status != PJ_SUCCESS)
            return -status;

        /* Write the default address */
        if (comp == 0) {
            /* For component 1, default address is in m= and c= lines */
            int port = (int) pj_sockaddr_get_port(&cand[0].addr);
            char* pAddr = pj_sockaddr_print(&cand[0].addr, ipaddr, sizeof (ipaddr), 0);
            printed = snprintf(p, maxlen - (p - buffer), "m=audio %d RTP/AVP 0\n" "c=IN IP4 %s\n", port, pAddr);
            p += printed;
        } else if (comp == 1) {
            /* For component 2, default address is in a=rtcp line */
            printed = snprintf(p, maxlen - (p - buffer), "a=rtcp:%d IN IP4 %s\n", (int) pj_sockaddr_get_port(&cand[0].addr), pj_sockaddr_print(&cand[0].addr, ipaddr, sizeof (ipaddr), 0));
            p += printed;
        } else {
            /* For other components, we'll just invent this.. */
            printed = snprintf(p, maxlen - (p - buffer), "a=Xice-defcand:%d IN IP4 %s\n", (int) pj_sockaddr_get_port(&cand[0].addr), pj_sockaddr_print(&cand[0].addr, ipaddr, sizeof (ipaddr), 0));
            p += printed;
        }

        /* Enumerate all candidates for this component */
        cand_cnt = PJ_ARRAY_SIZE(cand);
        status = pj_ice_strans_enum_cands(icest, comp + 1,
                &cand_cnt, cand);
        if (status != PJ_SUCCESS)
            return -status;

        /* And encode the candidates as SDP */
        for (j = 0; j < cand_cnt; ++j) {
            printed = printCandidate(p, maxlen - (unsigned) (p - buffer), &cand[j]);
            if (printed < 0)
                return -PJ_ETOOSMALL;
            p += printed;
        }
    }

    if (p == buffer + maxlen)
        return -PJ_ETOOSMALL;

    *p = '\0';
    return (int) (p - buffer);
}

/* Utility to create a=candidate SDP attribute */
int IceTransport::printCandidate(char buffer[], unsigned maxlen,
        const pj_ice_sess_cand *cand) {
    char ipaddr[PJ_INET6_ADDRSTRLEN];
    char *p = buffer;
    int printed;

    printed = snprintf(p, maxlen - (p - buffer), "a=candidate:%.*s %u UDP %u %s %u typ ", (int) cand->foundation.slen, cand->foundation.ptr, (unsigned) cand->comp_id, cand->prio, pj_sockaddr_print(&cand->addr, ipaddr, sizeof (ipaddr), 0), (unsigned) pj_sockaddr_get_port(&cand->addr));
    p += printed;

    printed = snprintf(p, maxlen - (p - buffer), "%s\n", pj_ice_get_cand_type_name(cand->type));
    p += printed;

    if (p == buffer + maxlen)
        return -PJ_ETOOSMALL;

    *p = '\0';

    return (int) (p - buffer);
}

int IceTransport::getALine(char* line, char* buffer, int startPos) {
    char* pBuffer = (buffer + startPos);
    int length = 0;

    while ((*pBuffer) != '\0') {
        *(line + length) = (*pBuffer);
        length++;
        pBuffer++;
        if ((*(pBuffer - 1)) == '\0') {
            break;
        }
        if (((*(pBuffer - 1)) == '\r')) {
            if ((*pBuffer) == '\n') {
                *(line + length) = (*pBuffer);
                length++;
                pBuffer++;
            }
            *(line + length) = '\0';
            break;
        } else if (((*(pBuffer - 1)) == '\n')) {
            if ((*pBuffer) == '\r') {
                *(line + length) = (*pBuffer);
                length++;
                pBuffer++;
            }
            *(line + length) = '\0';
            break;
        }
    }

    return length;
}

/*
 * Input and parse SDP from the remote (containing remote's ICE information) 
 * and save it to global variables.
 */

void IceTransport::setRemoteSDP(char* buffer) {
    char linebuf[80];
    unsigned media_cnt = 0;
    unsigned comp0_port = 0;
    char comp0_addr[80];
    pj_bool_t done = PJ_FALSE;


    puts("Paste SDP from remote host, end with empty line");

    resetRemoteInfo();

    comp0_addr[0] = '\0';

    int bufferLen = strlen(buffer);
    int position = 0;
    while (!done) {
        pj_size_t len;
        char *line;

        if (position >= bufferLen) {
            break;
        }
        position += getALine(linebuf, buffer, position);

        len = strlen(linebuf);
        while (len && (linebuf[len - 1] == '\r' || linebuf[len - 1] == '\n'))
            linebuf[--len] = '\0';

        line = linebuf;
        while (len && pj_isspace(*line))
            ++line, --len;

        if (len == 0)
            break;

        /* Ignore subsequent media descriptors */
        if (media_cnt > 1)
            continue;

        switch (line[0]) {
            case 'm':
            {
                int cnt;
                char media[32], portstr[32];

                ++media_cnt;
                if (media_cnt > 1) {
                    puts("Media line ignored");
                    break;
                }

                cnt = sscanf(line + 2, "%s %s RTP/", media, portstr);
                if (cnt != 2) {
                    PJ_LOG(1, (THIS_FILE, "Error parsing media line"));
                    goto on_error;
                }

                comp0_port = atoi(portstr);

            }
                break;
            case 'c':
            {
                int cnt;
                char c[32], net[32], ip[80];

                cnt = sscanf(line + 2, "%s %s %s", c, net, ip);
                if (cnt != 3) {
                    PJ_LOG(1, (THIS_FILE, "Error parsing connection line"));
                    goto on_error;
                }

                strcpy(comp0_addr, ip);
            }
                break;
            case 'a':
            {
                char *attr = strtok(line + 2, ": \t\r\n");
                if (strcmp(attr, "ice-ufrag") == 0) {
                    strcpy(rem.ufrag, attr + strlen(attr) + 1);
                } else if (strcmp(attr, "ice-pwd") == 0) {
                    strcpy(rem.pwd, attr + strlen(attr) + 1);
                } else if (strcmp(attr, "rtcp") == 0) {
                    char *val = attr + strlen(attr) + 1;
                    int af, cnt;
                    int port;
                    char net[32], ip[64];
                    pj_str_t tmp_addr;
                    pj_status_t status;

                    cnt = sscanf(val, "%d IN %s %s", &port, net, ip);
                    if (cnt != 3) {
                        PJ_LOG(1, (THIS_FILE, "Error parsing rtcp attribute"));
                        goto on_error;
                    }

                    if (strchr(ip, ':'))
                        af = pj_AF_INET6();
                    else
                        af = pj_AF_INET();

                    pj_sockaddr_init(af, &rem.def_addr[1], NULL, 0);
                    tmp_addr = pj_str(ip);
                    status = pj_sockaddr_set_str_addr(af, &rem.def_addr[1],
                            &tmp_addr);
                    if (status != PJ_SUCCESS) {
                        PJ_LOG(1, (THIS_FILE, "Invalid IP address"));
                        goto on_error;
                    }
                    pj_sockaddr_set_port(&rem.def_addr[1], (pj_uint16_t) port);

                } else if (strcmp(attr, "candidate") == 0) {
                    char *sdpcand = attr + strlen(attr) + 1;
                    int af, cnt;
                    char foundation[32], transport[12], ipaddr[80], type[32];
                    pj_str_t tmpaddr;
                    int comp_id, prio, port;
                    pj_ice_sess_cand *cand;
                    pj_status_t status;

                    cnt = sscanf(sdpcand, "%s %d %s %d %s %d typ %s",
                            foundation,
                            &comp_id,
                            transport,
                            &prio,
                            ipaddr,
                            &port,
                            type);
                    if (cnt != 7) {
                        PJ_LOG(1, (THIS_FILE, "error: Invalid ICE candidate line"));
                        goto on_error;
                    }

                    cand = &rem.cand[rem.cand_cnt];
                    pj_bzero(cand, sizeof (*cand));

                    if (strcmp(type, "host") == 0)
                        cand->type = PJ_ICE_CAND_TYPE_HOST;
                    else if (strcmp(type, "srflx") == 0)
                        cand->type = PJ_ICE_CAND_TYPE_SRFLX;
                    else if (strcmp(type, "relay") == 0)
                        cand->type = PJ_ICE_CAND_TYPE_RELAYED;
                    else {
                        PJ_LOG(1, (THIS_FILE, "Error: invalid candidate type '%s'",
                                type));
                        goto on_error;
                    }

                    cand->comp_id = (pj_uint8_t) comp_id;
                    pj_strdup2(pool, &cand->foundation, foundation);
                    cand->prio = prio;

                    if (strchr(ipaddr, ':'))
                        af = pj_AF_INET6();
                    else
                        af = pj_AF_INET();

                    tmpaddr = pj_str(ipaddr);
                    pj_sockaddr_init(af, &cand->addr, NULL, 0);
                    status = pj_sockaddr_set_str_addr(af, &cand->addr, &tmpaddr);
                    if (status != PJ_SUCCESS) {
                        PJ_LOG(1, (THIS_FILE, "Error: invalid IP address '%s'",
                                ipaddr));
                        goto on_error;
                    }

                    pj_sockaddr_set_port(&cand->addr, (pj_uint16_t) port);

                    ++rem.cand_cnt;

                    if (cand->comp_id > rem.comp_cnt)
                        rem.comp_cnt = cand->comp_id;
                }
            }
                break;
        }
    }

    if (rem.cand_cnt == 0 ||
            rem.ufrag[0] == 0 ||
            rem.pwd[0] == 0 ||
            rem.comp_cnt == 0) {
        PJ_LOG(1, (THIS_FILE, "Error: not enough info"));
        goto on_error;
    }

    if (comp0_port == 0 || comp0_addr[0] == '\0') {
        PJ_LOG(1, (THIS_FILE, "Error: default address for component 0 not found"));
        goto on_error;
    } else {
        int af;
        pj_str_t tmp_addr;
        pj_status_t status;

        if (strchr(comp0_addr, ':'))
            af = pj_AF_INET6();
        else
            af = pj_AF_INET();

        pj_sockaddr_init(af, &rem.def_addr[0], NULL, 0);
        tmp_addr = pj_str(comp0_addr);
        status = pj_sockaddr_set_str_addr(af, &rem.def_addr[0],
                &tmp_addr);
        if (status != PJ_SUCCESS) {
            PJ_LOG(1, (THIS_FILE, "Invalid IP address in c= line"));
            goto on_error;
        }
        pj_sockaddr_set_port(&rem.def_addr[0], (pj_uint16_t) comp0_port);
    }

    PJ_LOG(3, (THIS_FILE, "Done, %d remote candidate(s) added",
            rem.cand_cnt));
    return;

on_error:
    resetRemoteInfo();
}

/*
 * Start ICE negotiation! This function is invoked from the menu.
 */
void IceTransport::startNegotiation(void) {
    pj_str_t rufrag, rpwd;
    pj_status_t status;

    if (icest == NULL) {
        PJ_LOG(1, (THIS_FILE, "Error: No ICE instance, create it first"));
        return;
    }

    if (!pj_ice_strans_has_sess(icest)) {
        PJ_LOG(1, (THIS_FILE, "Error: No ICE session, initialize first"));
        return;
    }

    if (rem.cand_cnt == 0) {
        PJ_LOG(1, (THIS_FILE, "Error: No remote info, input remote info first"));
        return;
    }

    PJ_LOG(3, (THIS_FILE, "Starting ICE negotiation.."));

    status = pj_ice_strans_start_ice(icest,
            pj_cstr(&rufrag, rem.ufrag),
            pj_cstr(&rpwd, rem.pwd),
            rem.cand_cnt,
            rem.cand);
    if (status != PJ_SUCCESS) {
        printError("Error starting ICE", status);
    } else {
        PJ_LOG(3, (THIS_FILE, "ICE negotiation started"));
    }
}

/*
 * Send application data to remote agent.
 */
void IceTransport::sendData(unsigned comp_id, const char *data) {
    pj_status_t status;

    if (icest == NULL) {
        PJ_LOG(1, (THIS_FILE, "Error: No ICE instance, create it first"));
        return;
    }

    if (!pj_ice_strans_has_sess(icest)) {
        PJ_LOG(1, (THIS_FILE, "Error: No ICE session, initialize first"));
        return;
    }

    /*
    if (!pj_ice_strans_sess_is_complete(icedemo.icest)) {
        PJ_LOG(1,(THIS_FILE, "Error: ICE negotiation has not been started or is in progress"));
        return;
    }
     */

    if (comp_id < 1 || comp_id > pj_ice_strans_get_running_comp_cnt(icest)) {
        PJ_LOG(1, (THIS_FILE, "Error: invalid component ID"));
        return;
    }

    status = pj_ice_strans_sendto(icest, comp_id, data, strlen(data),
            &rem.def_addr[comp_id - 1],
            pj_sockaddr_get_len(&rem.def_addr[comp_id - 1]));
    if (status != PJ_SUCCESS) {
        printError("Error sending data", status);
    } else {
        PJ_LOG(3, (THIS_FILE, "Data sent"));
    }
}

